/*
 *    Copyright [2010] [Sébastien Gissinger]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

package com.kikoulol.jarmory.extractor.xml;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.kikoulol.jarmory.extractor.ExtractorConfig;

public abstract class AbstractXmlFile
{
  protected static String urlEncode(String urlToEncode)
  {
    // Placed in first position to avoid conflicts
    urlToEncode = urlToEncode.replace("%", "%25");

    urlToEncode = urlToEncode.replace(" ", "%20");
    urlToEncode = urlToEncode.replace("!", "%21");
    urlToEncode = urlToEncode.replace("\"", "%22");
    urlToEncode = urlToEncode.replace("#", "%23");
    urlToEncode = urlToEncode.replace("$", "%24");
    urlToEncode = urlToEncode.replace("&", "%26");
    urlToEncode = urlToEncode.replace("'", "%27");
    urlToEncode = urlToEncode.replace("(", "%28");
    urlToEncode = urlToEncode.replace(")", "%29");
    urlToEncode = urlToEncode.replace("*", "%2A");
    urlToEncode = urlToEncode.replace("+", "%2B");
    urlToEncode = urlToEncode.replace(",", "%2C");
    urlToEncode = urlToEncode.replace("-", "%2D");
    urlToEncode = urlToEncode.replace(".", "%2E");
    urlToEncode = urlToEncode.replace("/", "%2F");

    urlToEncode = urlToEncode.replace(":", "%3A");
    urlToEncode = urlToEncode.replace(";", "%3B");
    urlToEncode = urlToEncode.replace("<", "%3C");
    urlToEncode = urlToEncode.replace("=", "%3D");
    urlToEncode = urlToEncode.replace(">", "%3E");
    urlToEncode = urlToEncode.replace("?", "%3F");
    urlToEncode = urlToEncode.replace("@", "%40");

    urlToEncode = urlToEncode.replace("[", "%5B");
    urlToEncode = urlToEncode.replace("\\", "%5C");
    urlToEncode = urlToEncode.replace("]", "%5D");
    urlToEncode = urlToEncode.replace("^", "%5E");
    urlToEncode = urlToEncode.replace("_", "%5F");
    urlToEncode = urlToEncode.replace("`", "%60");

    urlToEncode = urlToEncode.replace("{", "%7B");
    urlToEncode = urlToEncode.replace("|", "%7C");
    urlToEncode = urlToEncode.replace("}", "%7D");
    urlToEncode = urlToEncode.replace("~", "%7E");

    urlToEncode = urlToEncode.replace("€", "%80");
    urlToEncode = urlToEncode.replace("‚", "%82");
    urlToEncode = urlToEncode.replace("ƒ", "%83");
    urlToEncode = urlToEncode.replace("„", "%84");
    urlToEncode = urlToEncode.replace("…", "%85");
    urlToEncode = urlToEncode.replace("†", "%86");
    urlToEncode = urlToEncode.replace("‡", "%87");
    urlToEncode = urlToEncode.replace("ˆ", "%88");
    urlToEncode = urlToEncode.replace("‰", "%89");
    urlToEncode = urlToEncode.replace("Š", "%8A");
    urlToEncode = urlToEncode.replace("‹", "%8B");
    urlToEncode = urlToEncode.replace("Œ", "%8C");
    urlToEncode = urlToEncode.replace("Ž", "%8E");

    urlToEncode = urlToEncode.replace("‘", "%91");
    urlToEncode = urlToEncode.replace("’", "%92");
    urlToEncode = urlToEncode.replace("“", "%93");
    urlToEncode = urlToEncode.replace("”", "%94");
    urlToEncode = urlToEncode.replace("•", "%95");
    urlToEncode = urlToEncode.replace("–", "%96");
    urlToEncode = urlToEncode.replace("—", "%97");
    urlToEncode = urlToEncode.replace("˜", "%98");
    urlToEncode = urlToEncode.replace("™", "%99");
    urlToEncode = urlToEncode.replace("š", "%9A");
    urlToEncode = urlToEncode.replace("›", "%9B");
    urlToEncode = urlToEncode.replace("œ", "%9C");
    urlToEncode = urlToEncode.replace("ž", "%9E");
    urlToEncode = urlToEncode.replace("Ÿ", "%9F");

    urlToEncode = urlToEncode.replace("¡", "%A1");
    urlToEncode = urlToEncode.replace("¢", "%A2");
    urlToEncode = urlToEncode.replace("£", "%A3");
    urlToEncode = urlToEncode.replace("¥", "%A5");
    urlToEncode = urlToEncode.replace("|", "%A6");
    urlToEncode = urlToEncode.replace("§", "%A7");
    urlToEncode = urlToEncode.replace("¨", "%A8");
    urlToEncode = urlToEncode.replace("©", "%A9");
    urlToEncode = urlToEncode.replace("ª", "%AA");
    urlToEncode = urlToEncode.replace("«", "%AB");
    urlToEncode = urlToEncode.replace("¬", "%AC");
    urlToEncode = urlToEncode.replace("¯", "%AD");
    urlToEncode = urlToEncode.replace("®", "%AE");
    urlToEncode = urlToEncode.replace("¯", "%AF");

    urlToEncode = urlToEncode.replace("°", "%B0");
    urlToEncode = urlToEncode.replace("±", "%B1");
    urlToEncode = urlToEncode.replace("²", "%B2");
    urlToEncode = urlToEncode.replace("³", "%B3");
    urlToEncode = urlToEncode.replace("´", "%B4");
    urlToEncode = urlToEncode.replace("µ", "%B5");
    urlToEncode = urlToEncode.replace("¶", "%B6");
    urlToEncode = urlToEncode.replace("·", "%B7");
    urlToEncode = urlToEncode.replace("¸", "%B8");
    urlToEncode = urlToEncode.replace("¹", "%B9");
    urlToEncode = urlToEncode.replace("º", "%BA");
    urlToEncode = urlToEncode.replace("»", "%BB");
    urlToEncode = urlToEncode.replace("¼", "%BC");
    urlToEncode = urlToEncode.replace("½", "%BD");
    urlToEncode = urlToEncode.replace("¾", "%BE");
    urlToEncode = urlToEncode.replace("¿", "%BF");

    urlToEncode = urlToEncode.replace("À", "%C0");
    urlToEncode = urlToEncode.replace("Á", "%C1");
    urlToEncode = urlToEncode.replace("Â", "%C2");
    urlToEncode = urlToEncode.replace("Ã", "%C3");
    urlToEncode = urlToEncode.replace("Ä", "%C4");
    urlToEncode = urlToEncode.replace("Å", "%C5");
    urlToEncode = urlToEncode.replace("Æ", "%C6");
    urlToEncode = urlToEncode.replace("Ç", "%C7");
    urlToEncode = urlToEncode.replace("È", "%C8");
    urlToEncode = urlToEncode.replace("É", "%C9");
    urlToEncode = urlToEncode.replace("Ê", "%CA");
    urlToEncode = urlToEncode.replace("Ë", "%CB");
    urlToEncode = urlToEncode.replace("Ì", "%CC");
    urlToEncode = urlToEncode.replace("Í", "%CD");
    urlToEncode = urlToEncode.replace("Î", "%CE");
    urlToEncode = urlToEncode.replace("Ï", "%CF");

    urlToEncode = urlToEncode.replace("Ð", "%D0");
    urlToEncode = urlToEncode.replace("Ñ", "%D1");
    urlToEncode = urlToEncode.replace("Ò", "%D2");
    urlToEncode = urlToEncode.replace("Ó", "%D3");
    urlToEncode = urlToEncode.replace("Ô", "%D4");
    urlToEncode = urlToEncode.replace("Õ", "%D5");
    urlToEncode = urlToEncode.replace("Ö", "%D6");
    urlToEncode = urlToEncode.replace("Ø", "%D8");
    urlToEncode = urlToEncode.replace("Ù", "%D9");
    urlToEncode = urlToEncode.replace("Ú", "%DA");
    urlToEncode = urlToEncode.replace("Û", "%DB");
    urlToEncode = urlToEncode.replace("Ü", "%DC");
    urlToEncode = urlToEncode.replace("Ý", "%DD");
    urlToEncode = urlToEncode.replace("Þ", "%DE");
    urlToEncode = urlToEncode.replace("ß", "%DF");

    urlToEncode = urlToEncode.replace("à", "%E0");
    urlToEncode = urlToEncode.replace("á", "%E1");
    urlToEncode = urlToEncode.replace("â", "%E2");
    urlToEncode = urlToEncode.replace("ã", "%E3");
    urlToEncode = urlToEncode.replace("ä", "%E4");
    urlToEncode = urlToEncode.replace("å", "%E5");
    urlToEncode = urlToEncode.replace("æ", "%E6");
    urlToEncode = urlToEncode.replace("ç", "%E7");
    urlToEncode = urlToEncode.replace("è", "%E8");
    urlToEncode = urlToEncode.replace("é", "%E9");
    urlToEncode = urlToEncode.replace("ê", "%EA");
    urlToEncode = urlToEncode.replace("ë", "%EB");
    urlToEncode = urlToEncode.replace("ì", "%EC");
    urlToEncode = urlToEncode.replace("í", "%ED");
    urlToEncode = urlToEncode.replace("î", "%EE");
    urlToEncode = urlToEncode.replace("ï", "%EF");

    urlToEncode = urlToEncode.replace("ð", "%F0");
    urlToEncode = urlToEncode.replace("ñ", "%F1");
    urlToEncode = urlToEncode.replace("ò", "%F2");
    urlToEncode = urlToEncode.replace("ó", "%F3");
    urlToEncode = urlToEncode.replace("ô", "%F4");
    urlToEncode = urlToEncode.replace("õ", "%F5");
    urlToEncode = urlToEncode.replace("ö", "%F6");
    urlToEncode = urlToEncode.replace("÷", "%F7");
    urlToEncode = urlToEncode.replace("ø", "%F8");
    urlToEncode = urlToEncode.replace("ù", "%F9");
    urlToEncode = urlToEncode.replace("ú", "%FA");
    urlToEncode = urlToEncode.replace("û", "%FB");
    urlToEncode = urlToEncode.replace("ü", "%FC");
    urlToEncode = urlToEncode.replace("ý", "%FD");
    urlToEncode = urlToEncode.replace("þ", "%FE");
    urlToEncode = urlToEncode.replace("ÿ", "%FF");

    return urlToEncode;
  }
  private int connectionTries = 0;
  private String webUrl;

  private Element xml = null;

  private final DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance();

  /**
   * 
   * @param webUrl
   */
  public AbstractXmlFile(String webUrl)
  {
    this.webUrl = webUrl;
  }

  /**
   * 
   * @param destFile
   * @throws IOException
   *           which are managed by setupXmlRootElement()
   */
  private final void downloadFile() throws IOException
  {
    DataInputStream in = null;
    FileOutputStream out = null;

    URL remoteFile = new URL(this.getWebUrl());
    URLConnection fileStream = remoteFile.openConnection();
    fileStream.setRequestProperty("User-Agent",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1");

    // Open the input streams for the remote file
    out = new FileOutputStream(this.getLocalFile());
    in = new DataInputStream(fileStream.getInputStream());

    // Read the remote on save save the file
    int data;
    while ((data = in.read()) != -1)
    {
      out.write(data);
    }

    in.close();
    out.flush();
    out.close();
  }

  /**
   * 
   * @param file
   * @return
   * @throws IOException
   *           which are managed by setupXmlRootElement()
   */
  private final String getFileString() throws IOException
  {
    // System.out.println("Reading from hd : " + aFile.getName());
    // ...checks on aFile are elided
    StringBuilder contents = new StringBuilder();

    // use buffering, reading one line at a time
    // FileReader always assumes default encoding is OK!
    FileReader fReader = new FileReader(this.getLocalFile());
    BufferedReader input = new BufferedReader(fReader);

    String line = null; // not declared within while loop

    /*
     * readLine is a bit quirky : it returns the content of a line MINUS the
     * newline. it returns null only for the END of the stream. it returns an
     * empty String if two newlines appear in a row.
     */
    while ((line = input.readLine()) != null)
    {
      contents.append(line);
      contents.append(System.getProperty("line.separator"));
    }
    input.close();

    return contents.toString();
  }

  /**
   * 
   * @param webUrl
   * @return
   */
  private final File getLocalFile()
  {
    String filePath = ExtractorConfig.getCacheFolder() + "/";
    filePath = filePath + this.getWebUrl().split("/")[2] + "-";
    filePath = filePath + this.getWebUrl().split("/")[3] + ".xml";
    filePath = filePath.replace("?", "-");
    
    File localFile = new File(filePath);
    localFile.getParentFile().mkdirs();
    
    return localFile;
  }

  /**
   * 
   * @return
   * @throws IOException
   *           which are managed by setupXmlRootElement()
   */
  private final String getXmlString() throws IOException
  {
    if (ExtractorConfig.isLogEnabled())
    {
      System.out.println("getting : " + this.getWebUrl());
    }
    
    if (this.getLocalFile().exists() && ExtractorConfig.isCacheActive())
    {
      return this.getFileString();
    }
    else
    {
      if (ExtractorConfig.isCacheActive())
      {
        this.downloadFile();
      }

      URL url = new URL(this.getWebUrl());
      ExtractorConfig.waitAntiSpamTime();
      URLConnection urlConn = url.openConnection();
      urlConn.setRequestProperty("User-Agent",
          "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1");
      InputStreamReader inStream = new InputStreamReader(urlConn.getInputStream());
      BufferedReader buff = new BufferedReader(inStream);

      StringBuffer reader = new StringBuffer();

      // Read and print the lines from index.html
      String nextLine = buff.readLine();
      while (nextLine != null)
      {
        reader.append(nextLine);
        nextLine = buff.readLine();
      }
      
      return reader.toString();
    }
  }

  /**
   * 
   * @param xmlString
   * @return
   */
  private final void setupXmlRootElement()
  {
    try
    {
      this.connectionTries++;
      DocumentBuilder constructeur = xmlFactory.newDocumentBuilder();

      InputStream stream = new ByteArrayInputStream(this.getXmlString().getBytes("UTF-8"));
      Document document = constructeur.parse(stream);
      
      this.xml = document.getDocumentElement();
      this.connectionTries = 0;
    }
    catch (Exception e)
    {
      if (this.getLocalFile().exists())
      {
        this.getLocalFile().delete();
      }

      System.out.println("Connection failed " + this.connectionTries + " of " + ExtractorConfig.getMaxTries());
      
      if (this.connectionTries < ExtractorConfig.getMaxTries())
      {
        System.out.println("Trying again ...");
        this.setupXmlRootElement();
      }
      else
      {
        System.out.println("Max connection tries done, file parsing cancelled !");
        this.xml = null;
      }
    }
  }

  /**
   * 
   * @param elementXml
   * @param elementTagName
   * @return
   */
  protected final String getItemElementNodeValue(Element elementXml, String elementTagName)
  {
    String str = null;

    try
    {
      str = elementXml.getElementsByTagName(elementTagName).item(0).getChildNodes().item(0).getNodeValue();
    }
    catch (NullPointerException e)
    {
    }
    return str;
  }

  /**
   * 
   * @param node
   * @param elementTagName
   * @return
   */
  protected final String getNamedItemValue(Node node, String elementTagName)
  {
    String str = null;

    try
    {
      str = node.getAttributes().getNamedItem(elementTagName).getNodeValue();
    }
    catch (NullPointerException e)
    {
    }
    return str;
  }

  protected abstract void initProperties();

  protected final void setupXml()
  {
    if (this.xml == null)
    {
      this.setupXmlRootElement();

      if (this.xml != null)
      {
        this.initProperties();
      }
    }
  }

  public final String getWebUrl()
  {
    return webUrl;
  }

  public final Element getXml()
  {
    return xml;
  }
}
